package com.github.cachex.support.shooting;

import com.github.cachex.exception.CacheXException;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;

import javax.annotation.PreDestroy;
import javax.sql.DataSource;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * @author jifang.zjf
 * @since 2017/6/9 下午12:35.
 */
public class DerbyShootingMXBeanImpl extends AbstractDBShootingMXBean {

    private static final String DERBY_JAR_REGEX = ".*/derby-([\\d\\.]+)\\.jar";

    private static final String DERBY_JAR_PATH = "%s/../db/lib/derby.jar";

    private static final String[] classPaths = {
            "sun.boot.class.path",
            "java.ext.dirs",
            "java.class.path"
    };

    public DerbyShootingMXBeanImpl() {
        this(System.getProperty("user.home") + "/.Derby");
    }

    public DerbyShootingMXBeanImpl(String derbyFilePath) {
        super(derbyFilePath, Collections.emptyMap());
    }

    @Override
    protected Supplier<JdbcOperations> jdbcOperationsSupplier(String dbPath, Map<String, Object> context) {
        return () -> {
            registerDerbyDriver();
            SingleConnectionDataSource dataSource = new SingleConnectionDataSource();
            dataSource.setUrl(String.format("jdbc:derby:%s;create=true", dbPath));
            JdbcOperations jdbcOperations = new JdbcTemplate(dataSource);
            try {
                if (isTableNotExists(dataSource)) {
                    jdbcOperations.execute("CREATE TABLE T_HIT_RATE (" +
                            "id            BIGINT PRIMARY KEY       GENERATED BY DEFAULT AS IDENTITY," +
                            "pattern       VARCHAR(64) NOT NULL UNIQUE," +
                            "hit_count     BIGINT      NOT NULL     DEFAULT 0," +
                            "require_count BIGINT      NOT NULL     DEFAULT 0," +
                            "version       BIGINT      NOT NULL     DEFAULT 0)");
                }
            } catch (SQLException e) {
                throw new CacheXException("derby create table: T_HIT_RATE error", e);
            }

            return jdbcOperations;
        };
    }

    private boolean isTableNotExists(DataSource dataSource) throws SQLException {
        return !dataSource
                .getConnection()
                .getMetaData()
                .getTables(null, null, "T_HIT_RATE", new String[]{"TABLE"})
                .next();
    }

    @Override
    protected Stream<AbstractDBShootingMXBean.DataDO> transferResults(List<Map<String, Object>> mapResults) {
        return mapResults.stream().map((map) -> {
            DataDO data = new DataDO();
            data.setPattern((String) map.get("PATTERN"));
            data.setHitCount((long) map.get("HIT_COUNT"));
            data.setRequireCount((long) map.get("REQUIRE_COUNT"));
            data.setVersion((long) map.get("VERSION"));
            return data;
        });
    }

    // --------------------- //
    // ---- Derby Driver --- //
    // --------------------- //
    private void registerDerbyDriver() {
        if (!containsDerbyJar()) {
            String jarFilePath = String.format(DERBY_JAR_PATH, System.getProperty("java.home"));
            if (new File(jarFilePath).exists()) {
                loadJar(jarFilePath);
            }
        }

        try {
            Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
        } catch (ClassNotFoundException e) {
            throw new CacheXException("derby.jar is not in classpath and not in ${JAVA_HOME}/db/lib directory, "
                    + "please make sure derby's drive has loaded in classpath", e);
        }
    }

    private boolean containsDerbyJar() {

        boolean contains = false;
        for (int i = 0; !contains && i < classPaths.length; ++i) {
            String jarStr;
            if (!Strings.isNullOrEmpty(jarStr = System.getProperty(classPaths[i]))) {
                for (String jar : Splitter.on(":").split(jarStr)) {
                    if (jar.matches(DERBY_JAR_REGEX)) {
                        contains = true;
                        break;
                    }
                }
            }
        }

        return contains;
    }

    private void loadJar(String javaFile) {
        URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();

        Method addURLMethod = null;
        try {
            addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        } catch (NoSuchMethodException ignored) {
        }
        assert addURLMethod != null;
        addURLMethod.setAccessible(true);
        try {
            addURLMethod.invoke(loader, new URL("file://" + javaFile));
        } catch (IllegalAccessException | InvocationTargetException | MalformedURLException ignored) {
        }
    }

    @PreDestroy
    public void tearDown() {
        super.tearDown();
    }
}
